/**
 * TypeScript 中提供了非常强大的类型推导能力，
 * 它会随着你的 代码逻辑 不断尝试收窄类型，
 * 这一能力称之为 类型的控制流分析（也可以简单理解为 类型推导 ）。
*/

/**
 * 比如，通过 if 条件中的表达式进行了类型保护，即告知了这里的分析程序 每个 if 语句代码块中变量会是何类型。
 * 这即是 编程语言的类型能力 中最重要的一部分：
 * 与实际逻辑紧密关联的类型，从 逻辑 中进行 类型地推导，再反过来让 类型 为 逻辑 保驾护航
*/
function foo(input: string | number) {
    if (typeof input === 'string') { }
    if (typeof input === 'number') { }
    // ...
}
/**
 * 但是，如果 if 条件中的表达式要是现在被提取出来了。
 * 因为 isString 这个函数在另外一个地方，内部的判断逻辑并不在函数 foo 中。
 * TypeScript的 类型控制流分析 做不到 跨函数上下文 来进行 类型的信息收集（但别的类型语言中可能是支持的）。
 * */
function isString(input: unknown): boolean {
    return typeof input === "string";
}
function fooA(input: string | number) {
    if (isString(input)) {
        // 类型“string | number”上不存在属性“replace”。
        (input).replace("hanxuming", "hanxuming599")
    }
    if (typeof input === 'number') { }
    // ...
}
/**
 * 实际上，将判断逻辑封装起来提取到函数外部进行复用非常常见
 * 为了解决这一类型控制流分析的能力不足， TypeScript 引入了 is 关键字来显式地提供类型信息：
 * 
 * isString 函数称为类型守卫，在它的返回值中，不再使用 boolean 作为类型标注，
 * 而是使用 input is string 这样的搭配，其中：
 * input 函数的某个参数；
 * is string，即 is 关键字 + 预期类型，
 * 即如果这个函数成功返回为 true，
 * 那么 is 关键字前这个入参的类型，就会被这个 类型守卫调用方 后续的 类型控制流分析 收集到。
*/
function isStringA(input: unknown): input is string {
    return typeof input === "string";
}
function fooB(input: string | number) {
    if (isStringA(input)) {
        // 正确了
        (input).replace("hanxuming", "hanxuming599")
    }
    if (typeof input === 'number') { }
    // ...
}

/**
 * 需要注意的是，类型守卫函数中并不会对判断逻辑和实际类型的关联进行检查：
 * 
 * 从这个角度来看，其实 类型守卫 有些类似于 类型断言，但类型守卫更宽容，也更信任你一些。
 * 你指定什么类型，它就是什么类型。
*/
function fooC(input: string | number) {
    if (isStringA(input)) {
        // 报错，在这里变成了 number 类型
        (input).replace("hanxuming", "hanxuming599")
    }
    if (typeof input === 'number') { }
    // ...
}

/**
 * 除了使用简单的原始类型以外，还可以在类型守卫中使用对象类型、联合类型等，
 * 比如下面开发时常用的两个守卫：
*/
export type Falsy = false | "" | 0 | null | undefined;
export const isFalsy = (val: unknown): val is Falsy => !val;

//不包括不常用的 symbol 和 bigint
export type Primitive = string | number | boolean | undefined;
export const isPrimitive = (val: unknown): val is Primitive => ['string', 'number', 'boolean' , 'undefined'].includes(typeof val);

//export {}：解决“无法重新声明块范围变量”错误提示问题
export { }